题目链接:https://github.com/giantbranch/CTF_PWN/tree/master/other/houseoforange
讲解题目
这是一个HITCON中的经典题目了,通过对buildhouse函数的逆向可以得到下面两个结构体
1 | struct house{ |
所以在ida新建结构体,当然这题好像没啥太大的帮助
1 | 00000000 orange struc ; (sizeof=0x8, mappedto_6) |
保护是全开的
1 | Arch: amd64-64-little |
总体思路:
1、通过upgrade的溢出覆盖topchunk size,那么我们再build,原来的topchunk就到unsortbin去了,就有了libc指针
注意:topchunk size的覆盖是有限制的,最低位要是1,跟topchunk的其实地址加起来要跟0x1000对齐,还有要大于MINSIZE (好像是0x10)
1 | ######### overwrite top chunk size |
2、接下来我们申请一个largebin大小的(因为有fd_nextsize和bk_nextsize,可以泄露heap的地址),我们只覆盖前8个字节就可以leak出libc了
注:64位下,largebin最小大小是0x400,即1024字节,要申请largebin大小,必须大于等于0x3e9
1 | ######### leak libc |
3、之后再覆盖0x10大小,泄露heap地址
1 | # leak heap |
4、最后就利用溢出覆盖unsortbin的bk,实行unsortbin attack,将_IO_list_all
指针指向了main_arena+0x58
,那么链表指针_chain
指向了smallbin[4],即第5个smallbin——0x60大小的。而我们之前就将unsortbin的size改为0x61,所以我们再申请的时候,他首先就会放到smallbin[4],最后我们伪造vtable即可
查看unsortbin位置,可以看到确实chain指向
1 | gdb-peda$ p *((struct _IO_FILE_plus*)0x7f742fb8db78) |
那么整个过程怎么触发的呢?
我们修改了bk后,那当我们再次申请内存的时候,其他bins都没有可用的,而unsortbin有可用的内存,就会遍历unsortbin列表,不合适就将bin拖链放到对应的bins列表,比如0x60大小的就放到smallbin那里了,这时候就通过unsortbin attack修改了_IO_list_all
,即下面代码的bck->fd = unsorted_chunks (av);
,将unsortbin的头指针写到了bck的fd,即我们要写到(_IO_list_all-0x10)->fd
,也即写到_IO_list_all
(注:av的类型是malloc_state,即mstate,他指向main_arena),再次遍历下一个时由于size为0即_IO_list_all-8
处为0,触发的异常,从而劫持控制力,详细请看附录
1 | while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av)) |
还有我们伪造的IO_FILE需要满足一些条件,才能调用_IO_OVERFLOW
1 | if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base) |
那么_IO_OVERFLOW
指针覆盖为system,fp头指针我们设置为/bin/sh
最终即可成功getshell
1 | [+] Starting local process './houseoforange_22785bece84189e632567da38e4be0e0c4bb1682': pid 6012 |
exp
1 | #!/usr/bin/env python |
附录——调试最后的流程劫持过程
首先下个硬件断点:watch _IO_list_all
断下来,由于开启了源码调试,我们看源码吧,bck->fd = unsorted_chunks (av);
就是将unsorted_bin的头指针写到_IO_list_all
的代码
1 | 3515 unsorted_chunks (av)->bk = bck; |
继续调试
下面的代码是不会进去的,因为这是刚好满足的情况
1 | /* Take now instead of binning if exact fit */ |
接下来是进入这里
1 | 3533 if (in_smallbin_range (size)) |
接下来的操作是,将这个chunk放到对应的bin上,比如这里就放到0x60的bins上
1 | mark_bin (av, victim_index); |
此时这个0x60大小的chunk已经放入smallbin了
1 | gdb-peda$ smallbin |
接下来就去下一个unsortbin的chunk了
1 | 3464 */ |
接下来由于_IO_list_all
那边的size为0
1 | gdb-peda$ x /20gx 0x7f32413d9510 |
具体可以看看调试,rsi为0,就是size为0
1 | ──────────────────────────────────────────────────────────────────[ REGISTERS ]────────────────────────────────────────────────────────────────── |
那么接下来就进入了malloc_printerr这个函数了
1 | ► 3474 malloc_printerr (check_action, "malloc(): memory corruption", |
接下来直接运行,崩溃,可以看到调用顺序是malloc_printerr---->__libc_message---->__GI_abort---->_IO_flush_all_lockp
1 | gdb-peda$ bt |
所以经过上面的调试你理解4ngelboy的图了吧
附录2:_IO_flush_all_lockp流程
首先获取头指针
1 | 768 _IO_lock_lock (list_all_lock); |
之后就到达判断处
1 | ► 779 if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base) |
由于第一个_IO_FILE_plus条件不满足直接跳过了
1 | gdb-peda$ p *(struct _IO_FILE_plus *)0x7f6d94f61b78 |
之后通过_chain获取下一个_IO_FILE_plus
1 | 795 /* Something was added to the list. Start all over again. */ |
满足条件就进入了if,执行_IO_OVERFLOW
1 | 781 || (_IO_vtable_offset (fp) == 0 |
最终执行的是我们的system,rdi也就是fp,就是/bin/sh,getshell没问题
1 | ────────────────────────────────────────────────────────────────────────────────[ REGISTERS ]──────────────────────────────────────────────────────────────────────────────── |
那个rax就是jump table了
1 | gdb-peda$ p *(struct _IO_jump_t *)$rax |
reference
http://4ngelboy.blogspot.com/2016/10/hitcon-ctf-qual-2016-house-of-orange.html
https://veritas501.space/2017/12/13/IO%20FILE%20%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/